package com.atguigu.tingshu.common.aspect;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.common.anno.CustomCache;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Type;

/**
 * Author  王东剑
 * Created on 19/6/2024 PM7:51
 * Package_name com.atguigu.tingshu.common.aspect
 * Description
 */
@Component
@Slf4j
@Aspect
public class CustomCacheAspect {
	@Autowired
	private StringRedisTemplate redisTemplate;
	@Autowired
	private RedissonClient redissonClient;

	@Around(value = "@annotation(customCache)")
	public Object around(ProceedingJoinPoint joinPoint, CustomCache customCache) {
		// 	取缓存数据
		// 获取缓存的key  格式例如: album:info:#{#params[0]}
		String cacheKey = customCache.cacheKey();
		// 获取参数
		Object[] args = joinPoint.getArgs();
		// 解析正确的 Redis key
		String parseDefaultName = this.parseDefaultName(joinPoint, args, cacheKey);
		// 根据这个 parseDefaultName key 查询 Redis, 如果存在直接解析返回,
		// 如果不存在,将这个切面方法的返回值作为 value, parseDefaultName 作为 key 存入 Redis
		String json = redisTemplate.opsForValue().get(parseDefaultName);
		if (!StringUtils.isEmpty(json)) {
			// 转换成方法返回值类型的数据返回
			MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
			// 这个 getGenericReturnType() 方法可以返回类型和泛型的类型,比较全面,接受使用的接口是 Type, FastJSON 支持这个类型
			Type genericReturnType = methodSignature.getMethod().getGenericReturnType();
			log.info("Redis 缓存命中,类型 genericReturnType: {}", genericReturnType);
			// 转换成对应类型返回
			return JSON.parseObject(json, genericReturnType);
		}
		// 	加分布式锁
		// 获取是否加锁,和锁名称
		boolean enableLock = customCache.enableLock();
		String lockName = customCache.lockName();
		// 是否加锁为 true,和锁名称 不为空才开始加锁
		if (enableLock && !StringUtils.isEmpty(lockName)) {
			// 	解析锁名称
			lockName = this.parseDefaultName(joinPoint, args, lockName);
			// 	尝试获取锁
			RLock rLock = redissonClient.getLock(lockName);
			if (rLock.tryLock()) {
				// 	加锁成功
				log.info("加锁成功 lockName: {}", lockName);
				// 	进行业务
				try {
					Object result = joinPoint.proceed();
					log.info("数据成功保存至Redis :{}", result);
					redisTemplate.opsForValue().set(parseDefaultName, JSON.toJSONString(result));
					return result;
				} catch (Throwable e) {
					throw new RuntimeException(e);
				} finally {
					rLock.unlock();
				}
			} else {
				// 未加锁成功,自旋
				around(joinPoint, customCache);
			}
		}
		// 	不加分布式锁
		// 	进行业务
		try {
			Object result = joinPoint.proceed();
			log.info("数据成功保存至Redis :{}", result);
			redisTemplate.opsForValue().set(parseDefaultName, JSON.toJSONString(result));
			// 	返回数据
			return result;
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 将注解里面的cacheKey的一部分内容解析并放入这个cacheKey里指定的该方法的第几个参数
	 * 比如这个标注了注解的方法,将里面的  #{#params[0]}  解析为第一个参数 里的变量值albumId:
	 * @CustomCache(cacheKey = RedisConstant.CACHE_CATEGORY_PREFIX+"#{#params[0]}")
	 * 	public BaseCategoryView findBaseCategoryView(Long albumId)
	 * @param joinPoint 连接点
	 * @param args 方法参数
	 * @param cacheKey 缓存的表达式
	 * @return 一个完整的 redisKey
	 */
	private String parseDefaultName(ProceedingJoinPoint joinPoint, Object[] args, String cacheKey) {
		// 解析 album:info:#{#params[0]} 格式的数据,解析完成的结果类似 album:info:1
		SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
		// ParserContext.TEMPLATE_EXPRESSION  设置解析的开始结束  开始是 #{  结束是 }
		Expression expression = spelExpressionParser.parseExpression(cacheKey, ParserContext.TEMPLATE_EXPRESSION);
		StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext();
		// 将这个数组里面的值放入 standardEvaluationContext 对象里面的 params 数组里面
		standardEvaluationContext.setVariable("params", args);
		// 开始解析并返对应的值
		return expression.getValue(standardEvaluationContext, String.class);
	}
}
